
print('#=======================内置函数==============')

#绝对值
x = abs(-100) #100
print(x)

#最大值，最小值
x = max(1,3,4,3,4,564)
print(x)
x = min(1,3,4,3,4,564)
print(x)

#len(obj)
#print(str)


print('#=======================自定义函数==============')
'''
没有return，则返回结果是None
返回多个值，则返回的是一个tuple
'''
def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

#定义个函数，什么也不干
def nop():
    pass

import math

#返回多个值：其实返回的是个tuple
def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny
x, y = move(100, 100, 60, math.pi / 6)
print(x,y)
s = move(100, 100, 60, math.pi / 6)
print(s)


print('#=======================默认参数=============')
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

print(power(3))
print(power(3,4))

'''
下面这个是默认参数的一个坑：原则是默认参数必须指向一个不变对象：
否则，在函数调用之前，默认参数对应的变量的值就已经计算出来了，
如果函数实现中改变了默认参数，则会对之后的调用产生副作用
===甚至，如果将默认参数返回了，则外界的操作还会影响下回调用时这个默认的参数的值
'''
#传入一个列表，在列表最后添加一个END元素，然后返回
def add_end(L=[]):
    L.append('END')
    return L

print(add_end([1,2,3]))
print(add_end())
print(add_end())

#上面的函数可以这么修改：
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

print('#=======================可变参数=============')

#用下面的*语法，可以指定参数个人是可变参数，其实就是指定我这里的numbers是个tuple
def calc(name, *numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return '%s--%d' %(name, sum)

print(calc('哈哈'))
print(calc('哈哈', 1))
print(calc('哈哈', 1,2))
print(calc('哈哈', 1,2,3))
print(calc('哈哈', 1,2,3.4))

print('#=======================关键字参数=============')
'''
和关键字参数对应的就是之前的普通参数，也就是位置参数
'''

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)


person('Michael', 30)
person('Adam', 45, gender='M', job='Engineer')

extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)  #注意kw获得的dict是extra的一份拷贝，对kw的改动不会影响到函数外的extra


print('#=======================命名关键字参数=============')
'''
限定能够传入的关键字参数的key
命名关键字参数需要一个特殊分隔符*，*后面的参数被视为命名关键字参数
'''
def person(name, age, *, city, job):
    print(name, age, city, job)

person('Jack', 24, city='Beijing', job='Engineer')

#关键字参数的默认值
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)

person('Jack', 24, job='Engineer')

print('#=======================混合使用各种参数=============')
'''
==可变参数无法和命名关键字参数混合
==请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数/命名关键字参数， 关键字参数

注意：
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)

你会发现不管你怎么定义f1，总是可以通过一个tuple和一个dict来调用f1！！！
只不过区别在于args里的参数个数和kw里对key及key的顺序可能有要求

'''


'''
Python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数。

默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误！

要注意定义可变参数和关键字参数的语法：

*args是可变参数，args接收的是一个tuple；

**kw是关键字参数，kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法：

可变参数既可以直接传入：func(1, 2, 3)，又可以先组装list或tuple，再通过*args传入：func(*(1, 2, 3))；

关键字参数既可以直接传入：func(a=1, b=2)，又可以先组装dict，再通过**kw传入：func(**{'a': 1, 'b': 2})。

使用*args和**kw是Python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值。

定义命名的关键字参数不要忘了写分隔符*，否则定义的将是位置参数。
'''


print('#=======================递归和尾递归=============')
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

print(fact(10))

'''
看上面的写法，返回值依赖于n和函数本身，也就是计算了下一级fact，才能返回这一级fact，
但是如果想办法仅仅返回函数本身，编译器就能对递归做一个优化，优化的直接结果就是递归
不再依赖于层级的栈帧，只依赖于一个，不会造成栈溢出
'''
def fact2(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

print(fact2(10))


print('#=======================自定义可callable的类=============')
class CS(object):

    #此对象可以通过obj()来调用，并且会导致callable(obj)返回true
    def __call__(self):
        print('我是个可调用的对象')

cs = CS()
if callable(cs):
    cs()

print('#======================指向函数的变量=============')
def add(x, y ,f):
    return f(x) + f(y)

print(add(-5, 6, abs))

print('#======================map/reduce=============')
'''
先看个实际的需求：把函数f作用在list的每个元素上，并生成一个新列表
'''

def f(x):
    return x*x

print('\n#====传统本办法')
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)

print('\n#====使用map函数：接受一个函数和一个list作为参数，意思不言而明了')
r = map(f, [1,2,3,4,5,6,7])
L = list(r)
print(L)

print('\n#====reduce：接受的函数f必须有两个参数，每一次f的返回值都作为下一次f的第一个参数')
#求和
from functools import reduce
def add(x, y):
    return x + y
sum = reduce(add, [1,2,3,4,5])
print(sum)


print('\n#================fitler函数：过滤掉不感兴趣的元素======================')
#主要是需要实现一个筛选函数，作用于每一个元素，返回True，则对应元素保留
def is_odd(n):
    return n%2 == 1

#filter返回一个Iterator，是个惰性序列，所以为了强迫得到计算结果，需要使用list函数
L = list(filter(is_odd, [1,2,3,4,5,6,7,8,9]))
print(L)


print('\n#================sorted函数：排序======================')

print("\n#=======对数值排序：默认")
L = sorted([36, 5, -12, 9, -21])
print(L)

print("\n#=======对数值排序：按绝对值")
L = sorted([36, 5, -12, 9, -21], key=abs)
print(L)

print("\n#=======对数值排序：按绝对值，倒序")
L = sorted([36, 5, -12, 9, -21], key=abs, reverse=True)
print(L)

print("\n#=======对字符串排序：默认")
L = sorted(['bob', 'about', 'Zoo', 'Credit'])
print(L)

print("\n#=======对字符串排序：默认")
L = sorted(['1', '2', '3', '10'])
print(L)

print("\n#=======对字符串排序：数字式的字符串默认行为不太对")
def by_value(t):
    return int(t)
    return name
L = sorted(['1', '2', '3', '10'], key=by_value)
print(L)

print("\n#=======对字符串排序：按小写（也就是忽略大小写）")
L = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
print(L)

print("\n#=======对字符串排序")
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

# 根据name来排序
def by_name(t):
    name = t[0]
    return name

L2 = sorted(L, key=by_name)
print(L2)


print('\n#================闭包Closure：返回一个函数======================')
'''
内部函数sum可以引用外部函数lazy_sum的参数和局部变量，当lazy_sum返
回函数sum时，相关参数和变量都保存在返回的函数中，这种称为“闭包（Closure）”
的程序结构拥有极大的威力
'''

# 返回了一个函数：返回的函数是带有传入的参数信息的，所以可以从一个地方给另一个地方返回个带数据的闭包
def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum   #注意，这里每次调用lazy_sum返回的都是一个全新的函数

f = lazy_sum(1, 3, 5, 7, 9)
print(f)
print(f())
print(f())

f2 = lazy_sum(1, 3, 5, 7, 9)
print(f == f2)

# 重点：返回的函数引用的是外层函数中的变量，并且只有在运行时，所引用的变量的值才是最终值，看下面的例子

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
#你可能认为调用f1()，f2()和f3()结果应该是1，4，9，但实际结果是：9,9,9
#因为调用f1,f2,f3时，i的值是几？是3


print('\n\n#================匿名函数：lambda表达式======================')
ite = map(lambda x : x * x, [1,2,3,4,5,6])
L = list(ite)
print(type(ite))
print(L)

'''
===Python对匿名函数的支持有限，只有一些简单的情况下可以使用匿名函数。
===这里的lambda表达式相当于：
def f(x):
    return x * x
'''

print('\n\n#================装饰器======================')

# 装饰器：无参数的
#定义一个函数，输出日志，记录调用了哪个函数
def log(func):
    def wrapper(*args, **kw):
        print('日志--call %s(), 参数是%s' % (func.__name__, args[0]))
        return func(*args, **kw)
    return wrapper

# 装饰器：带参数的
def log2(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

#@log
@log2('装饰器参数1')  #调用了log2(1).log2(2)
@log2('装饰器参数2')  #调用了log2(2).now
def now(param):
    print('now函数执行了----%s' %(param))

now('haha')

'''
装饰器的更多内容可以参考：http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000
例如：
===函数名的传递：__name__的传递
'''


print('\n\n#===============偏函数：固定住某些参数值，可以作为默认参数的补充======================')
'''
对于函数int('123')，默认当作10进制处理，如果要当二进制，需要：int('1111', base=2)
如果需要转换大量二进制，你还会想封装个函数：
def int2(s):
    return int(s, base=2)

python里有个直接语法支持：
import functools
int2 = functools.partial(int, base=2)
'''
import functools
int2 = functools.partial(int, base=2)
print(int2('10101010'))












#
